﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace SX.Utils
{
    /// <summary>
    /// String 扩展
    /// </summary>
    public static class StringExt
    {
        #region Appending

        /// <summary>
        /// 重复多次文字
        /// </summary>
        /// <param name="str">重复的文字</param>
        /// <param name="times">次数</param>
        /// <returns></returns>
        public static string Times(this string str, int times)
        {
            if (string.IsNullOrEmpty(str)) return string.Empty;
            if (times <= 1) return str;

            string strfinal = string.Empty;
            for (int ndx = 0; ndx < times; ndx++)
                strfinal += str;

            return strfinal;
        }

        #endregion Appending

        #region Truncation

        /// <summary>
        /// 截断字符串
        /// </summary>
        /// <param name="txt"> 字符串.</param>
        /// <param name="maxChars">最大长度.</param>
        /// <returns>截断字符串.</returns>
        public static string Truncate(this string txt, int maxChars)
        {
            if (string.IsNullOrEmpty(txt))
                return txt;

            if (txt.Length <= maxChars)
                return txt;

            return txt.Substring(0, maxChars);
        }

        /// <summary>
        /// 截断字符串并添加后缀
        /// </summary>
        /// <param name="txt">字符串.</param>
        /// <param name="maxChars">最大长度.</param>
        /// <param name="suffix">添加后缀.</param>
        /// <returns>截断字符串并添加后缀.</returns>
        public static string TruncateWithText(this string txt, int maxChars, string suffix)
        {
            if (string.IsNullOrEmpty(txt))
                return txt;

            if (txt.Length <= maxChars)
                return txt;

            // Now do the truncate and more.
            string partial = txt.Substring(0, maxChars);
            return partial + suffix;
        }

        #endregion Truncation

        #region Conversion

        public static bool ToBool(this string txt)
        {
            if (string.IsNullOrEmpty(txt))
                return false;
            return ConvertHelper.ToBool(txt);
        }

        public static int ToInt(this string txt)
        {
            return ConvertHelper.ToInt(txt);
        }

        public static long ToLong(this string txt)
        {
            return ConvertHelper.ToInt64(txt, 0);
        }

        public static double ToDouble(this string txt)
        {
            return ConvertHelper.ToDouble(txt, 0);
        }

        public static float ToFloat(this string txt)
        {
            return ConvertHelper.ToFloat(txt, 0);
        }

        #endregion Conversion

        #region Hex and Binary

        /// <summary>
        /// 是否是16进制
        /// </summary>
        /// <param name="txt">String to check.</param>
        /// <returns>True if the string contains valid hexadecimal characters.</returns>
        /// <remarks>An empty or null string is considered to <b>not</b> contain
        /// valid hexadecimal characters.</remarks>
        public static bool IsHex(this string txt)
        {
            return (!txt.IsNullOrEmpty()) && (txt.ReplaceChars("0123456789ABCDEFabcdef", "                      ").Trim().IsNullOrEmpty());
        }

        /// <summary>
        /// 是否是二进制
        /// </summary>
        /// <param name="txt">String to check.</param>
        /// <returns>True if the string contains valid binary characters.</returns>
        /// <remarks>An empty or null string is considered to <b>not</b> contain
        /// valid binary characters.</remarks>
        public static bool IsBinary(this string txt)
        {
            return (!txt.IsNullOrEmpty()) && (txt.ReplaceChars("01", "  ").Trim().IsNullOrEmpty());
        }

        /// <summary>
        /// 转换为16进制
        /// </summary>
        /// <param name="txt">Hexadecimal string to convert to decimal.</param>
        /// <returns>Decimal representation of string.</returns>
        public static string DecimalToHex(this string txt)
        {
            return Convert.ToInt32(txt).ToHex();
        }

        /// <summary>
        /// 转换为Binary(2进制)
        /// </summary>
        /// <param name="txt">Decimal string to convert to binary.</param>
        /// <returns>Binary representation of string.</returns>
        public static string DecimalToBinary(this string txt)
        {
            return Convert.ToInt32(txt).ToBinary();
        }

        /// <summary>
        /// Returns the decimal representation of a hexadecimal number.
        /// </summary>
        /// <param name="txt">Hexadecimal string to convert to decimal.</param>
        /// <returns>Decimal representation of string.</returns>
        public static string HexToDecimal(this string txt)
        {
            return Convert.ToString(Convert.ToInt32(txt, 16));
        }

        /// <summary>
        /// Returns the binary representation of a hexadecimal number.
        /// </summary>
        /// <param name="txt">Binary string to convert to hexadecimal.</param>
        /// <returns>Hexadecimal representation of string.</returns>
        public static string HexToBinary(this string txt)
        {
            return Convert.ToString(Convert.ToInt32(txt, 16), 2);
        }

        /// <summary>
        /// Converts a hexadecimal string to a byte array representation.
        /// </summary>
        /// <param name="txt">Hexadecimal string to convert to byte array.</param>
        /// <returns>Byte array representation of the string.</returns>
        /// <remarks>The string is assumed to be of even size.</remarks>
        public static byte[] HexToByteArray(this string txt)
        {
            byte[] b = new byte[txt.Length / 2];
            for (int i = 0; i < txt.Length; i += 2)
            {
                b[i / 2] = Convert.ToByte(txt.Substring(i, 2), 16);
            }
            return b;
        }

        /// <summary>
        /// Converts a byte array to a hexadecimal string representation.
        /// </summary>
        /// <param name="b">Byte array to convert to hexadecimal string.</param>
        /// <returns>String representation of byte array.</returns>
        public static string ByteArrayToHex(this byte[] b)
        {
            return BitConverter.ToString(b).Replace("-", "");
        }

        /// <summary>
        /// Returns the hexadecimal representation of a binary number.
        /// </summary>
        /// <param name="txt">Binary string to convert to hexadecimal.</param>
        /// <returns>Hexadecimal representation of string.</returns>
        public static string BinaryToHex(this string txt)
        {
            return Convert.ToString(Convert.ToInt32(txt, 2), 16);
        }

        /// <summary>
        /// Returns the decimal representation of a binary number.
        /// </summary>
        /// <param name="txt">Binary string to convert to decimal.</param>
        /// <returns>Decimal representation of string.</returns>
        public static string BinaryToDecimal(this string txt)
        {
            return Convert.ToString(Convert.ToInt32(txt, 2));
        }

        #endregion Hex and Binary

        #region Replacement

        /// <summary>
        /// Replaces the characters in the originalChars string with the
        /// corresponding characters of the newChars string.
        /// </summary>
        /// <param name="txt">String to operate on.</param>
        /// <param name="originalChars">String with original characters.</param>
        /// <param name="newChars">String with replacement characters.</param>
        /// <example>For an original string equal to "123456654321" and originalChars="35" and
        /// newChars "AB", the result will be "12A4B66B4A21".</example>
        /// <returns>String with replaced characters.</returns>
        public static string ReplaceChars(this string txt, string originalChars, string newChars)
        {
            string returned = "";

            for (int i = 0; i < txt.Length; i++)
            {
                int pos = originalChars.IndexOf(txt.Substring(i, 1));

                if (-1 != pos)
                    returned += newChars.Substring(pos, 1);
                else
                    returned += txt.Substring(i, 1);
            }
            return returned;
        }

        #endregion Replacement

        #region Lists

        /// <summary>
        /// Prefixes all items in the list w/ the prefix value.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <param name="prefix">The prefix.</param>
        /// <returns>List with prefixes.</returns>
        public static List<string> PreFixWith(this List<string> items, string prefix)
        {
            for (int ndx = 0; ndx < items.Count; ndx++)
            {
                items[ndx] = prefix + items[ndx];
            }
            return items;
        }

        #endregion Lists

        #region Matching

        /// <summary>
        /// Determines whether or not the string value supplied represents a "not applicable" string value by matching on na, n.a., n/a etc.
        /// </summary>
        /// <param name="val">String to check.</param>
        /// <param name="useNullOrEmptyStringAsNotApplicable">True to use null or empty string check.</param>
        /// <returns>True if the string value represents a "not applicable" string.</returns>
        public static bool IsNotApplicableValue(this string val, bool useNullOrEmptyStringAsNotApplicable = false)
        {
            bool isEmpty = string.IsNullOrEmpty(val);
            if (isEmpty && useNullOrEmptyStringAsNotApplicable) return true;
            if (isEmpty && !useNullOrEmptyStringAsNotApplicable) return false;
            val = val.Trim().ToLower();

            if (val == "na" || val == "n.a." || val == "n/a" || val == "n\\a" || val == "n.a" || val == "not applicable")
                return true;
            return false;
        }

        /// <summary>
        /// Use the Levenshtein algorithm to determine the similarity between
        /// two strings. The higher the number, the more different the two
        /// strings are.
        /// TODO: This method needs to be rewritten to handle very large strings
        /// See <a href="http://www.merriampark.com/ld.htm"></a>.
        /// See <a href="http://en.wikipedia.org/wiki/Levenshtein_distance"></a>.
        /// </summary>
        /// <param name="source">Source string to compare</param>
        /// <param name="comparison">Comparison string</param>
        /// <returns>0 if both strings are identical, otherwise a number indicating the level of difference</returns>
        public static int Levenshtein(this string source, string comparison)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source", "Can't parse null string");
            }
            if (comparison == null)
            {
                throw new ArgumentNullException("comparison", "Can't parse null string");
            }

            var s = source.ToCharArray();
            var t = comparison.ToCharArray();
            var n = source.Length;
            var m = comparison.Length;
            var d = new int[n + 1, m + 1];

            // shortcut calculation for zero-length strings
            if (n == 0) { return m; }
            if (m == 0) { return n; }

            for (var i = 0; i <= n; d[i, 0] = i++) { }
            for (var j = 0; j <= m; d[0, j] = j++) { }

            for (var i = 1; i <= n; i++)
            {
                for (var j = 1; j <= m; j++)
                {
                    var cost = t[j - 1].Equals(s[i - 1]) ? 0 : 1;

                    d[i, j] = Math.Min(Math.Min(
                        d[i - 1, j] + 1,
                        d[i, j - 1] + 1),
                        d[i - 1, j - 1] + cost);
                }
            }

            return d[n, m];
        }

        /// <summary>
        /// Calculate the simplified soundex value for the specified string.
        /// See <a href="http://en.wikipedia.org/wiki/Soundex"></a>.
        /// See <a href="http://west-penwith.org.uk/misc/soundex.htm"></a>.
        /// </summary>
        /// <param name="source">String to calculate</param>
        /// <returns>Soundex value of string</returns>
        public static string SimplifiedSoundex(this string source)
        {
            return source.SimplifiedSoundex(4);
        }

        /// <summary>
        /// Calculate the simplified soundex value for the specified string.
        /// See <a href="http://en.wikipedia.org/wiki/Soundex"></a>.
        /// See <a href="http://west-penwith.org.uk/misc/soundex.htm"></a>.
        /// </summary>
        /// <param name="source">String to calculate</param>
        /// <param name="length">Length of soundex value (typically 4)</param>
        /// <returns>Soundex value of string</returns>
        public static string SimplifiedSoundex(this string source, int length)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (source.Length < 3)
            {
                throw new ArgumentException(
                    "Source string must be at least two characters", "source");
            }

            var t = source.ToUpper().ToCharArray();
            var buffer = new StringBuilder();

            short prev = -1;

            foreach (var c in t)
            {
                short curr = 0;
                switch (c)
                {
                    case 'A':
                    case 'E':
                    case 'I':
                    case 'O':
                    case 'U':
                    case 'H':
                    case 'W':
                    case 'Y':
                        curr = 0;
                        break;

                    case 'B':
                    case 'F':
                    case 'P':
                    case 'V':
                        curr = 1;
                        break;

                    case 'C':
                    case 'G':
                    case 'J':
                    case 'K':
                    case 'Q':
                    case 'S':
                    case 'X':
                    case 'Z':
                        curr = 2;
                        break;

                    case 'D':
                    case 'T':
                        curr = 3;
                        break;

                    case 'L':
                        curr = 4;
                        break;

                    case 'M':
                    case 'N':
                        curr = 5;
                        break;

                    case 'R':
                        curr = 6;
                        break;

                    default:
                        throw new ApplicationException(
                            "Invalid state in switch statement");
                }

                /* Change all consecutive duplicate digits to a single digit
                 * by not processing duplicate values.
                 * Ignore vowels (i.e. zeros). */
                if (curr != prev)
                {
                    buffer.Append(curr);
                }

                prev = curr;
            }

            // Prefix value with first character
            buffer.Remove(0, 1).Insert(0, t.First());

            // Remove all vowels (i.e. zeros) from value
            buffer.Replace("0", "");

            // Pad soundex value with zeros until output string equals length))))
            while (buffer.Length < length) { buffer.Append('0'); }

            // Truncate values that are longer than the supplied length
            return buffer.ToString().Substring(0, length);
        }

        #endregion Matching

        /// <summary>
        /// 正则替换
        /// 例子：
        /// 只保留字符串中的数字字母（RegexRelace(  input, "([^a-zA-Z0-9])","")）
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="partern">The partern.</param>
        /// <param name="replace">The replace.</param>
        /// <returns>System.String.</returns>
        public static string RegexRelace(this string input, string partern, string replace)
        {
            return Regex.Replace(input, partern, replace);
        }

        /// <summary>
        /// 去除符号， 只保留数字和字母
        /// </summary>
        /// <param name="input">输入值.</param>
        /// <returns>System.String.</returns>
        public static string RemoveSymbol(this string input)
        {
            return RegexRelace(input, "([^a-zA-Z0-9])", "");
        }

        /// <summary>
        /// 是否包括符号
        /// </summary>
        /// <param name="input">输入值.</param>
        /// <returns>如果<c>真</c>  XXXX, 否则<c>假</c> .</returns>
        public static bool HaveSymbol(this string input)
        {
            return !Regex.IsMatch(input, "^[a-zA-Z0-9]*$");
        }
    }
}